package mygame;

import com.jme3.math.FastMath;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f;
import com.jme3.renderer.Camera;

/**
 * Cares for the control of the camera.
 * @author Aicher
 * @version 0.0.2
 */
public class MyCamWrapper {
    /**
     * With this speed the camera will be turned if a key is hit
     */
    public static final float KEY_CAM_TURN_SPEED = 1f;
    
    /**
     * With this speed the camera will be moved within the environment. The value
     * is currently quite high, so we can easily check the complete environment
     */
    public static final float KEY_CAM_MOVE_SPEED = 500f;    
    
    /**
     * Reference to the JME camera
     */
    private Camera myCam = null;
    
    /**
     * This Quaternion will be used for facilitating all camera rotations
     */
    private Quaternion camRotation = new Quaternion();
    
    /**
     * The angles of the camera (x,y and z rotating axis)
     */
    private float[] camAngles = new float[3];
    
    /**
     * Position of the camera in the coordinate system
     */
    private Vector3f camPosition = new Vector3f(0f, 0f, 0f);

    /**
     * Set to true, if the camera should turn right
     */
    private boolean doTurnRight = false;
        
    /**
     * Set to true, if the camera should turn left
     */
    private boolean doTurnLeft = false;
        
    /**
     * Set to true, if the camera should pitch up
     */
    private boolean doPitchUp = false;
        
    /**
     * Set to true, if the camera should pitch down
     */
    private boolean doPitchDown = false;
        
    /**
     * Set to true, if the camera should move upwards
     */
    private boolean doMoveUp = false;
        
    /**
     * Set to true, if the camera should move downwards
     */
    private boolean doMoveDown = false;
        
    /**
     * Set to true, if the camera should move left
     */
    private boolean doMoveLeft = false;
        
    /**
     * Set to true, if the camera should move right
     */
    private boolean doMoveRight = false;
        
    /**
     * Set to true, if the camera should move forward
     */
    private boolean doMoveForward = false;
        
    /**
     * Set to true, if the camera should move backward
     */
    private boolean doMoveBack = false;
    
    /**
     * Reference to the plane where the camera should be mounted
     */
    private MyPlane refPlane = null;
    
    /**
     * Constructor with the reference to the JME camera
     * @param myCam 
     */
    public MyCamWrapper(Camera myCam) {
        this.myCam = myCam;
    }
    
    /**
     * Cyclically called method for realizing the dynamic positioning of
     * the camera
     * @param tpf 
     */
    public void positionCam(float tpf) {
        if (this.doTurnLeft) {
            this.rotateCamDir(KEY_CAM_TURN_SPEED, tpf);
        } else if (this.doTurnRight) {
            this.rotateCamDir(-KEY_CAM_TURN_SPEED, tpf);
        } else if (this.doPitchUp) {
            this.rotateCamPitch(KEY_CAM_TURN_SPEED, tpf);
        } else if (this.doPitchDown) {
            this.rotateCamPitch(-KEY_CAM_TURN_SPEED, tpf);
        } else if (this.doMoveLeft) {
            this.moveCamPos(KEY_CAM_MOVE_SPEED, 0, 0, tpf);
        } else if (this.doMoveRight) {
            this.moveCamPos(-KEY_CAM_MOVE_SPEED, 0, 0, tpf);
        } else if (this.doMoveUp) {
            this.moveCamPos(0, KEY_CAM_MOVE_SPEED, 0, tpf);
        } else if (this.doMoveDown) {
            this.moveCamPos(0, -KEY_CAM_MOVE_SPEED, 0, tpf);
        } else if (this.doMoveForward) {
            this.moveCamPos(0, 0, KEY_CAM_MOVE_SPEED, tpf);
        } else if (this.doMoveBack) {
            this.moveCamPos(0, 0, -KEY_CAM_MOVE_SPEED, tpf);
        }

        // first check, if the reference plane exists
        if (this.refPlane != null) {
            
            // now place the camera to the cam position
            myCam.setLocation(camPosition.add( // and add...
                    refPlane.planeNode.getLocalRotation().mult(Vector3f.UNIT_Y) //..in Y direction of the plane
                    .mult(PlaneBehaviour.PILOT_VIEW_Y_POS_OFFSET[this.refPlane.planeBehaviour]) // the Y offset
                    .add(refPlane.planeNode.getLocalRotation().mult(Vector3f.UNIT_Z) //and in Z direction of the plane
                    .mult(PlaneBehaviour.PILOT_VIEW_Z_POS_OFFSET[this.refPlane.planeBehaviour]) // the Z offset
                    )));
            
            // now rotate the camera towards the plane rotation, added by the camRotation angles. So the
            // camera is positioned relativeley to the plane rotation
            myCam.setRotation(this.refPlane.planeNode.getLocalRotation().mult(camRotation.fromAngles(camAngles)));
        } else {
            // if the reference plane does not exist, position the cam absolute
            this.myCam.setRotation(camRotation.fromAngles(camAngles));
            this.myCam.setLocation(camPosition);
        }
    }
    
    
    
    /**
     * Initial setting of the camera position and angles
     * @param newCamPosition Position of the cam
     * @param newCamAngles Direction of the cam
     */
    public void setPosition(Vector3f newCamPosition, float[] newCamAngles) {
        this.camPosition = newCamPosition;
        this.camAngles = newCamAngles;
    }
    /**
     * Rotates the camera around the Y axis
     * @param deltaCamAngle Angle to be added
     * @param tpf 
     */
    public void rotateCamDir(float deltaCamAngle, float tpf) {
        addAngle(camAngles, deltaCamAngle * tpf, 1);
    }
    
    /**
     * Rotates the camera height angle
     *
     * @param deltaCamHeightAngle Angle to be added
     * @param tpf
     */
    public void rotateCamPitch(float deltaCamHeightAngle, float tpf) {
        addAngle(camAngles, deltaCamHeightAngle * tpf, 0);
    }        
    
    /**
     * Moves the camera towards the delta values
     * @param deltaX 
     * @param deltaY
     * @param deltaZ
     * @param tpf 
     */
    public void moveCamPos(float deltaX, float deltaY, float deltaZ, float tpf) {
        this.camPosition.set(
                this.camPosition.x += deltaX * tpf,
                this.camPosition.y += deltaY * tpf,
                this.camPosition.z += deltaZ * tpf);
    }    
    /**
     * Adds an angle to the given "angles" array on the givne position. The Method
     * limits the angles between +/- PI. 
     * @param angles Array where the value will be added
     * @param deltaValue Value which will be added
     * @param pos Position in the array which will be manipulated
     */
    public static void addAngle(float[] angles, float deltaValue, int pos) {
        angles[pos] += deltaValue;
        if (angles[pos] > FastMath.PI) {
            angles[pos] -= FastMath.TWO_PI;
        } else if (angles[pos] < -FastMath.PI) {
            angles[pos] += FastMath.TWO_PI;
        }
    }
    
    /**
     * Setter of doTurnRight
     * @param doTurnRight
     */
    public void setTurnRight(boolean doTurnRight) {
        this.doTurnRight = doTurnRight;
    }    
    
    /**
     * Setter of doTurnLeft
     * @param doTurnLeft 
     */
    public void setTurnLeft(boolean doTurnLeft) {
        this.doTurnLeft = doTurnLeft;
    }    
    
    /**
     * Setter of doPitchUp
     * @param doPitchUp 
     */
    
    public void setPitchUp(boolean doPitchUp) {
        this.doPitchUp = doPitchUp;
    }    
    
    /**
     * Setter of doPitchDown
     * @param doPitchDown 
     */
    public void setPitchDown(boolean doPitchDown) {
        this.doPitchDown = doPitchDown;
    }    
    
    /**
     * Setter of doMoveUp
     * @param doMoveUp
     */
    public void setMoveUp(boolean doMoveUp) {
        this.doMoveUp = doMoveUp;
    }    
    
    /**
     * Setter of doMoveDown
     * @param doMoveDown 
     */
    public void setMoveDown(boolean doMoveDown) {
        this.doMoveDown = doMoveDown;
    }
    
    /**
     * Setter of doMoveLeft
     * @param doMoveLeft 
     */
    public void setMoveLeft(boolean doMoveLeft) {
        this.doMoveLeft = doMoveLeft;
    }    
    
    /**
     * Setter of doMoveRight
     * @param doMoveRight 
     */
    public void setMoveRight(boolean doMoveRight) {
        this.doMoveRight = doMoveRight;
    }    
    
    /**
     * Setter of doMoveForward
     * @param doMoveForward 
     */
    public void setMoveForward(boolean doMoveForward) {
        this.doMoveForward = doMoveForward;
    }    
    
    /**
     * Setter of doMoveBack
     * @param doMoveBack 
     */
    public void setMoveBack(boolean doMoveBack) {
        this.doMoveBack = doMoveBack;
    }  
    
    /**
     * Setter of the reference plane
     * @param refPlane 
     */
    public void setPlane(MyPlane refPlane) {
        // take over the reference
        this.refPlane = refPlane;
        
        // and set the position towards the plane node. This is a real reference
        // and not a clone, so every plane movement will lead to a cam movement
        this.camPosition = refPlane.planeNode.getLocalTranslation();
    }

}
